package com.lm.statemachine.config;


import com.lm.statemachine.entry.Order;
import com.lm.statemachine.enums.OrderState;
import com.lm.statemachine.enums.OrderEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.config.StateMachineBuilder;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
import org.springframework.statemachine.listener.StateMachineListener;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.state.State;

import java.util.EnumSet;
import java.util.Objects;

@Configuration
public class StateMachineConfig extends StateMachineConfigurerAdapter<OrderState, OrderEvent> {

    private static final Logger log = LoggerFactory.getLogger(StateMachineConfig.class);

    @Bean
    public StateMachineListener<OrderState, OrderEvent> listener() {
        return new StateMachineListenerAdapter<OrderState, OrderEvent>() {
            @Override
            public void stateChanged(State<OrderState, OrderEvent> from, State<OrderState, OrderEvent> to) {
                log.info("State change to: {}", to.getId());
            }

            @Override
            public void stateMachineError(StateMachine<OrderState, OrderEvent> stateMachine, Exception exception) {
                log.error("Exception caught: {}", exception.getMessage(), exception);
            }

            @Override
            public void eventNotAccepted(Message<OrderEvent> message) {
                Order order = (Order) message.getHeaders().get("order");
                log.error("Order state machine can't change state {} --> {}", Objects.requireNonNull(order).getState(), message.getPayload());
            }
        };
    }

    @Bean
    public StateMachine<OrderState, OrderEvent> stateMachine(StateMachineListener<OrderState, OrderEvent> listener) throws Exception {
        StateMachineBuilder.Builder<OrderState, OrderEvent> builder = StateMachineBuilder.builder();

        builder.configureStates()
                .withStates()
                .initial(OrderState.CREATED)
                .end(OrderState.COMPLETED)
                .end(OrderState.CANCELLED)
                .states(EnumSet.allOf(OrderState.class));

        builder.configureTransitions()
                .withExternal()
                .source(OrderState.CREATED).target(OrderState.PAID).event(OrderEvent.PAY)
                .and()
                .withExternal()
                .source(OrderState.PAID).target(OrderState.SHIPPED).event(OrderEvent.SHIP)
                .and()
                .withExternal()
                .source(OrderState.SHIPPED).target(OrderState.COMPLETED).event(OrderEvent.COMPLETE)
                .and()
                .withExternal()
                .source(OrderState.CREATED).target(OrderState.CANCELLED).event(OrderEvent.CANCEL)
                .and()
                .withExternal()
                .source(OrderState.PAID).target(OrderState.CANCELLED).event(OrderEvent.CANCEL);

        StateMachine<OrderState, OrderEvent> stateMachine = builder.build();
        stateMachine.addStateListener(listener);
        return stateMachine;
    }
}
